<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>underscorejs</title>
	</head>
	<body style="height: 3000px;">
		<h1>underscorejs</h1>

		<!-- <script src="./js/underscore.js"></script> -->
		<script>
			(function(root) {
				// root === window
				const toString = Object.prototype.toString;

				function cb(callback, context) {
					if (callback == null) {
						return function(value) {
							return value;
						}
					}

					if (_.isFunction(callback)) {
						return optimizeCb(callback, context)
					}
				}

				function optimizeCb(callback, context, count = 3) {
					if (context == void 0) {
						return callback;
					}
					switch (count) {
						case 1:
							return function(value) {
								return callback.call(context, value)
							}
						case 3:
							return function(value, index, list) {
								return callback.call(context, value, index, list);
							}
						case 4:
							return function(meno, value, index, list) {
								return callback.call(context, meno, value, index, list);
							}
					}
				}

				function baseCreate(_prototype) {
					// 针对Object.create做的polyfill处理
					if (!_.isObject(_prototype)) return {};
					if (Object.create) return Object.create(_prototype);
					let rest = new Object();
					rest.__proto__ = _prototype;
					return rest;
				}

				_ = function(obj) {
					if (obj instanceof _) {
						return obj;
					}

					if (!(this instanceof _)) {
						console.log('new');
						return new _(obj)
					}
					this._wrapped = obj;
				}
				/* 工具函数 */
				_.isArray = function (obj) {
					return toString.call(obj) === '[object Array]';
				}
				_.each = function(target, callback) {
					let key, i = 0;
					if (_.isArray(target)) {
						let len = target.length;
						for (; i < len; i++) {
							callback.call(target, target[i], i)
						}
					} else {
						for (key in target) {
							callback.call(target, target[key], key)
						}
					}
				}
				_.each(['Arguments', 'Boolean', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set',
					'WeakSet'
				], function(name) {
					_['is' + name] = function(obj) {
						return toString.call(obj) === '[object ' + name + ']';
					};
				});
				_.functions = function(obj) {
					// 作用等同于 Object.keys
					let key, ret = [];
					for (key in obj) {
						ret.push(key)
					}
					return ret;
				}
				_.restArguments = function(func) {
					let startIndex = func.length - 1;
					return function() {
						let len = arguments.length;
						let ret = [],
							i = 0;
						ret[startIndex] = [];
						for (; i < len; i++) {
							if (i < startIndex) {
								ret[i] = arguments[i]
							} else {
								ret[startIndex].push(arguments[i])
							}
				
						}
						console.log(ret)
						func.apply(this, ret)
					};
				}
				
				_.sortedIndex = function (arr, item, callback, context) {
					let iteratee = cb(callback, context);
					item = iteratee(item);
					let first = 0, last = arr.length - 1;
					let mid;
					while(first < last){
						mid = Math.floor((last + first)/2);
						if(iteratee(arr[mid]) < item){
							first = mid + 1;
						}else{
							last = mid;
						}
					}
					return first;
				}
				
				/* 数组函数 */
				_.unique = function(arr, isSort, callback, context) {
					if(!_.isBoolean(isSort)){
						context = callback;
						callback = isSort;
						isSort = false;
					}
					
					let iteratee = callback && cb(callback, context);
					// 数组去重
					let ret = [], pre;
					let target, i = 0;
					for (; i < arr.length; i++) {
						target = iteratee ? iteratee(arr[i]) : arr[i];
						if(isSort){
							console.log('排序');
							if(i==0 || pre !== target) ret.push(target);
							pre = target;
						}else if (ret.indexOf(target) === -1) {
							ret.push(target);
						}
					}
					return ret;
				}
				_.map = function(arr, callback, context) {
					let iteratee = cb(callback, context);
					// 处理arr是object的情况
					let ret = [];
					let i = 0;

					let keys = !_.isArray(arr) && Object.keys(arr);
					let len = (keys || arr).length;
					let item;
					for (; i < len; i++) {
						item = keys ? keys[i] : i
						ret[i] = iteratee(arr[item], item, arr);
					}
					return ret;
				}
				function createReduce(dir) {
					let reduce = function (arr, callback, meno, init) {
						let keys = !_.isArray(arr) && Object.keys(arr);
						let len = (keys || arr).length;
						let i = dir > 0 ? 0 : (len - 1);
						if(init){
							// 这里直接判断 meno == void 0 不好吗
							meno = arr[keys ? keys[i] : i];
							i += dir;
						}
						for(;i >= 0 && i < len; i+=dir){
							let key = keys ? keys[i] : i;
							meno = callback(meno, arr[key], key, arr);
						}
						return meno;
					}
					return function (arr, callback, meno, context) {
						let init = arguments.length < 3;
						return reduce(arr, optimizeCb(callback, context, 4), meno, init)
					}
				}
				_.reduce = createReduce(1);
				_.reduceRight = createReduce(-1);
				_.filter = _.select = function (arr, callback, context) {
					let iteratee = cb(callback, context);
					let res = [];
					_.each(arr, function (item, index, arr) {
						if(iteratee(item, index, arr)) res.push(item);
					})
					return res;
				}
				function createIndexOf(dir, findFunc) {
					return function (arr, item, idx) {
						let i = 0, len = arr.length;
						
						if(dir > 0 && idx === true && len){
							idx = _.sortedIndex(arr, item);
							return arr[idx] === item ? idx : -1;
						}
						
						if(item !== item){
							// 如果传过来的item是 NaN
							// 处理
						}
						
						for(i = dir > 0 ? 0 : (len - 1); i >= 0 && i < len; i += dir){
							if(arr[i] == item)	return i;
						}
						
						return -1;
					}
				}
				_.indexOf = createIndexOf(1);
				_.lastIndexOf = createIndexOf(-1);
				// 摊平数组
				_.flatten = function (arr, nodeep) {
					let ret = [], 
						i = 0, value,
						len = arr.length;
						
					for(;i < len; i++){
						value = arr[i]
						if(_.isArray(value)){
							if(!nodeep){
								value = _.flatten(value)
							}
							
							for(let j = 0; j < value.length; j++){
								ret.push(value[j])
							}
						}else{
							ret.push(value)
						}
					}
					return ret;
				}
				// 获取范围内指定数量的数组
				_.range = function (start, stop, step) {
					if(stop == null){
						stop = start || 0;
						start = 0
					}
					step = step || 1;
					let ret = [], i = 0;
					for(;start < stop; start += step){
						ret[i] = start;
						i++;
					}
					return ret;
				}
				// 获取随机数
				_.random = function (min, max) {
					if(max == null){
						max = min;
						min = 0
					}
					return min + Math.floor(Math.random()*(max-min+1))
				}
				// 偏函数，柯里化？
				_.partial = function (func) {
					let args = [].slice.call(arguments, 1);
					let bound = function(){
						let ret = args.concat([].slice.call(arguments,0));
						return func.apply(this, ret)
					}
					return bound;
				}
				// 递归函数的 memorize cache 缓存
				_.memoize = function (func, hash) {
					let memoize = function(key){
						let cache = memoize.cache;
						
						key = hash ? hash.apply(this, arguments) : key;
						
						if(!cache.hasOwnProperty(key)){
							cache[key] = func.apply(this, arguments);
						}
						return cache[key];
					}
					memoize.cache = {};
					return memoize;
				}
				// setTimeout的封装，方便给延时执行的函数传参
				_.delay = function(func, wait) {
					let args = [].slice.call(arguments, 2);
					return setTimeout(function() {
						return func.apply(null, args);
					}, wait);
				};
				// 函数组合器，从右到左将一个函数的返回值给到另一个函数作为参数，如此循环
				_.compose = function () {
					let args = arguments;
					let end = args.length - 1;
					return function () {
						let f_args = args[end--].apply(null, arguments);
						for(;0 <= end; end--){
							f_args = args[end].call(null, f_args);
						}
						return f_args;
					}
				}
				// 字符串逃逸(防止XSS攻击),对特殊字符进行转义
				const escapeMap = {
					'<': '&lt;',
					'>': '&gt;',
					'&': '&amp;',
					'"': '&quot;',
					"'": '&apos;',
					'￠': '&cent;',
					'£': '&pound;',
					'¥': '&yen;',
					'€': '&euro;',
					'§': '&sect;',
					'©': '&copy;',
					'®': '&reg;',
					'™': '&trade;',
					'×': '&times;',
					'÷': '&divide;'
				}
				function createEscape() {
					let source = '(?:' + Object.keys(escapeMap).join('|') + ')';
					let textExp = new RegExp(source, 'g');
					return function (str){
						return textExp.test(str) ? str.replace(textExp, function(match){
							return escapeMap[match];
						}) : str;
					}
				}
				_.escape = createEscape();
				// 节流
				_.throttle = function (func, wait, options) {
					let timer = null;
					options = options || {};
					return function(){
						let args = arguments;
						
						if(options.leading !== false){
							func.apply(null, args);
							options.leading = false;
						}
						
						if(timer == null){
							timer = setTimeout(function(){
								func.apply(null, args)
								timer = null;
							}, wait)
						}
					}
				}
				// 防抖
				_.debounce = function (func, wait) {
					let timer = null;
					return function(){
						let args = arguments;
						
						// if(options.leading !== false){
						// 	func.apply(null, args);
						// 	options.leading = false;
						// }
						
						if(timer){
							clearTimeout(timer);
							timer = setTimeout(function(){
								func.apply(null, args)
							}, wait)
						}
					}
				}
				_.clone = function (arr) {
					// 缺陷：如果是object类型，应该将其添加一个length属性
					return _.isArray(arr) ? [...arr] : {...arr};
				}
				
				// 洗牌算法
				_.sample = function (arr, n) {
					if(n == null){
						return arr[_.random(arr.length - 1)];
					}
					
					let array = _.clone(arr);
					let len = array.length;
					n = len > n ? n : len;
					for(let i = 0; i < n; i++){
						let temp = _.random(arr.length - 1);
						[array[i], array[temp]] = [array[temp], array[i]]
					}
					return array.slice(0, n)
				}
				
				// 基于洗牌算法的乱序算法
				_.shuffle = function (arr) {
					return _.sample(arr, Infinity);
				}

				/* chain通道，链式调用 */
				_.prototype.value = function() {
					return this._wrapped;
				}
				_.chain = function(obj) {
					let instance = _(obj);
					instance._chain = true;
					return instance;
				}

				function result(target, value) {
					if(target._chain){
						target._wrapped = value;
						return target;
					}
					return value;
				}

				_.mixin = function(obj) {
					_.each(_.functions(obj), function(name) {
						let func = obj[name];
						_.prototype[name] = function() {
							let args = this._wrapped;
							return result(this, func.call(this, args, ...arguments))
						};
					})
				}


				_.mixin(_);
				root._ = _
			})(window)
		</script>
		<script>
			console.log(_([1,2,3,4,5,4,3,3,3,4,6,7,1]).chain().unique().unique().unique().unique());
			// console.log(_('123123123123121231239').indexOf(9));
		</script>
	</body>
</html>
